package com.kaijie.utils;

import com.kaijie.exception.RRException;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

public class NumberUtils {

    /**
     * 设定一个基数，以免number值过小导致encode的字符过短
     */
    private final static long hexFixValue = 1000000000000001L;
    private final static String hexKeyMap = "ZDSx1HpzYmdq2WfavLKRleNUkji764QPGsubEMC5cXT30tFVwrJBnyOho89gA";

    /**
     * 对一个数值进行加密生成一串字符(每次生成的都不一样)
     */
    public static String hexEncode(Long number) {
        number = Math.addExact(number, hexFixValue); //先加上基数
        String[] hexArray = Long.toHexString(number).split("");
        String[] keyArray = hexKeyMap.split("");
        int keyLength = keyArray.length;
        Random rdm = new Random();
        int rand = rdm.ints(0, keyLength - 1).findFirst().getAsInt();
        StringBuilder result = new StringBuilder(keyArray[rand]);
        int r = (keyLength - rand + number.toString().length()) % keyLength;
        String verify = keyArray[r];
        for (String hs : hexArray) {
            int offset = Integer.parseInt(hs, 16) + rand;
            result.append(keyArray[offset % keyLength]);
        }
        result.append(verify);
        return result.toString();
    }

    /**
     * 对一个加密数值字符进行解密
     */
    public static Long hexDecode(String str) {
        if (!BaseUtils.regexMatch("^[0-9a-zA-Z]{6,18}$", str)) {
            throw new RRException("字符格式不正确");
        }
        String[] strArray = str.split("");
        List<String> strList = new LinkedList<>(Arrays.asList(strArray));
        String[] keyArray = hexKeyMap.split("");
        int keyLength = hexKeyMap.length();
        StringBuilder hexString = new StringBuilder();
        int rand = hexKeyMap.indexOf(strList.remove(0));
        String verify = strList.remove(strList.size() - 1);
        for (String s : strList) {
            int index = hexKeyMap.indexOf(s);
            if (index >= rand) {
                hexString.append(Integer.toHexString(index - rand));
            } else {
                hexString.append(Integer.toHexString(keyLength - rand + index));
            }
        }
        long number = Long.parseLong(hexString.toString(), 16);
        int r = (keyLength - rand + Long.toString(number).length()) % keyLength;
        if (!verify.equals(keyArray[r])) {
            throw new RRException("给定字符校验错误，无法解析");
        }

        return Math.subtractExact(number, hexFixValue);
    }

//---------------------------------------------divider line------------------------------------------------------

    /**
     * 设定一个基数，以免id值过小导致encode的字符过短
     */
    private final static long idFixValue = 100000000000001L;
    private final static String idKeyMap = "GQ4JF9VCABH3M2PY1O6DIXL0US7TNR8KWZE5";

    /**
     * 对ID值进行加密生成一串字符(每次生成都一样)
     */
    public static String idEncode(Long id) {
        long number = Math.addExact(id, idFixValue); //先加上基数
        char[] keyChars = idKeyMap.toCharArray();
        int keyLength = keyChars.length;
        StringBuilder codeSb = new StringBuilder();
        while(number > 0) {
            Long mod = number % keyLength;
            number = Math.subtractExact(number, mod) / keyLength;
            codeSb.append(keyChars[mod.intValue()]);
        }
        codeSb.append("S");
        codeSb.reverse();
        return codeSb.toString();
    }

    /**
     * 对一个加密ID字符进行解密
     */
    public static Long idDecode(String idCode) {
        String code = idCode.substring(1);
        int keyLength = idKeyMap.length();
        char[] codeChars = code.toCharArray();
        StringBuilder codeSb = new StringBuilder();
        codeSb.append(codeChars);
        codeSb.reverse();

        long number = 0L;
        for(int i=0; i<codeChars.length; i++) {
            int index = idKeyMap.indexOf(codeSb.charAt(i));
            if (index < 0) { //字符超出key map范围，不是有效的字符
                return 0L;
            }
            Double pow = Math.pow(keyLength, i);
            long val = Math.multiplyExact(index, pow.longValue());
            number = Math.addExact(number, val);
        }
        return Math.subtractExact(number, idFixValue);
    }

}
